#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time      :2021/7/7 14:31
# @Author    :cjw
from datetime import datetime, date
from pathlib import Path
from pydantic import BaseModel, ValidationError, constr
from typing import List, Optional

from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base

"""
Pydantic介绍：
1、使用python的类型注解来进行数据校验和settings管理
2、pydantic可以在代码运行时提供类型提示，数据校验失败时提供友好的错误提示
3、定义数据应该如何在纯规范的python代码中保存，并用pydantic验证它
"""

print("\033[31m1. --- Pydantic的基本用法。Pycharm可以安装Pydantic插件 ---\033[0m")


class User(BaseModel):
	id: int  # 没有默认值，则为必填字段
	name: str = 'John Snow'  # 有默认值，选填字段
	signup_ts: Optional[datetime] = None
	friends: List[int] = []  # 列表中元素是int类型或者可以直接转换成int类型


external_data = {
	'id': '123',
	'signup_ts': '2021-07-07 14:43:59',
	'friends': [1, 2, '3']  # '3'是可以int('3')转换成数字3的
}

user = User(**external_data)
print(user.id, user.friends)  # 实例化后调用属性
print(user.signup_ts)
print(user.dict())

print("\033[31m2. --- 校验失败处理 ---\033[0m")
try:
	User(id=1, signup_ts=datetime.today(), friends=[1, 2, 'not number'])
except ValidationError as e:
	print(e.json())  # 捕获的错误可以进行格式化

print("\033[31m3. --- 模型类的属性和方法 ---\033[0m")
print(f'转化为字典格式：{user.dict()}')  # 单引号
print(f'转化为json格式：{user.json()}')  # 双引号
print(f'进行浅拷贝：：{user.copy()}')
print(f'字典数据解析为User对象：：{User.parse_obj(obj=external_data)}')
raw_str = '{"id": "321", "signup_ts": "2021-07-07 15:10:59", "friends": [3,2,"1"]}'
print(f'解析原生数据：：{User.parse_raw(raw_str)}')

path = Path('pydantic_tutorial.json')  # 创建文件
path.write_text(raw_str)
print(f'解析文件，调用类方法：{User.parse_file(path)}')

print(user.schema())
print(user.schema_json())
user_data = {"id": "error", "signup_ts": "2021-07-07 15:10:59", "friends": [3, 2, "1"]}
# 不检验数据直接创建模型类，不建议在construct方法中传入未经验证的数据
print(f'不校验数据解析: {User.construct(**user_data)}')
# 定义模型类的时候，所有字段都注明类型，字段顺序就不会乱
print(f'类所有字段：{User.__fields__.keys()}')

print("\033[31m4. --- 递归模型：就是模型嵌套 ---\033[0m")


class Sound(BaseModel):
	sound: str


class Dag(Sound):
	birthday: date
	weight: Optional[float] = None
	sound: List[Sound]  # 不同的狗有不同的叫声。递归模型（Recursive Models）就是指一个嵌套一个


dogs = Dag(birthday=date.today(), weight=6.66, sound=[{"sound": "wang wang ~"}, {"sound": "ying ying ~"}])
print(dogs.dict())

print("\033[31m5. --- ORM模型：从类实例创建符合ORM对象的模型 ---\033[0m")
Base = declarative_base()


class CompanyOrm(Base):
	__tablename__ = 'companies'

	id = Column(Integer, primary_key=True, nullable=False)
	public_key = Column(String(20), index=True, nullable=False, unique=True)
	name = Column(String(60), unique=True)
	domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
	id: int
	public_key: constr(max_length=20)
	name: constr(max_length=63)
	domains: List[constr(max_length=255)]

	class Config:  # 表示建立的数据格式和模型类CompanyOrm是对应的
		orm_mode = True


co_orm = CompanyOrm(
	id=123,
	public_key='footbar',
	name='Testing',
	domains=['example.com', 'baidu.com']
)

print(f'数据表的模型类转化为定义的数据格式： {CompanyModel.from_orm(co_orm)}')

print("\033[31m6. --- Pydantic支撑的字段类型  ---\033[0m")
# 官方文档：https://pydantic-docs.helpmanual.io/usage/types/
